package bibliotheque.wave;

import java.util.ArrayList;
import java.util.Hashtable;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.concurrent.Semaphore;

public abstract class WaveAgent extends UnicastRemoteObject implements WaveAgentRemote {
    
    private class ThreadInitiate extends Thread {
        private WaveAction wave_action;
        
        public ThreadInitiate(WaveAction wave_action) {
            this.wave_action = wave_action;
        }
        
        public void run() {
            Wave wave = null;
            ArrayList<WaveAgentRemote> wave_waits = null;
            
            WaveAgent.this.mutex.acquireUninterruptibly();
            this.wave_action.wave_id = new WaveId(WaveAgent.this.ip, WaveAgent.this.hl++);
            wave_waits = (ArrayList<WaveAgentRemote>)WaveAgent.this.neighbours.clone();
            wave = new Wave(this.wave_action.wave_id, null, wave_waits);
            WaveAgent.this.waves.put(this.wave_action.wave_id, wave);
            WaveAgent.this.mutex.release();
            
            try {
                wave.mutex.acquireUninterruptibly();
                this.wave_action.PropagateBefore(WaveAgent.this);
                for (int i = 0;i<wave_waits.size();i++) {
                    wave_waits.get(i).Propagate(WaveAgent.this, this.wave_action);
                }
                this.wave_action.PropagateAfter(WaveAgent.this);
                if(WaveAgent.this.neighbours.size() == 0) {
                    this.wave_action.Acknowledge(WaveAgent.this);
                    this.wave_action.LocalEnd(WaveAgent.this);
                    this.wave_action.GlobalEnd(WaveAgent.this);
                }
                wave.first_mutex.release();
            } catch (Exception ex) {
                WaveError(ex.getMessage());
            } finally {
                wave.mutex.release();
            }
        }
    }
    
    private class ThreadPropagate extends Thread {
        private WaveAgentRemote wave_agent;
        private WaveAction wave_action;
        
        public ThreadPropagate(WaveAgentRemote wave_agent, WaveAction wave_action) {
            this.wave_agent = wave_agent;
            this.wave_action = wave_action;
        }
        
        public void run() {
            Wave wave = null;
            WaveAgent.PropagateState state = null;
            ArrayList<WaveAgentRemote> wave_waits = null;
            
            WaveAgent.this.mutex.acquireUninterruptibly();
            if(WaveAgent.this.neighbours.size() == 1) {
                state = WaveAgent.PropagateState.LEEF;
            } else if(WaveAgent.this.waves.containsKey(wave_action.wave_id)) {
                wave = WaveAgent.this.waves.get(wave_action.wave_id);
                state = WaveAgent.PropagateState.SECOND;
            } else {
                wave_waits = (ArrayList<WaveAgentRemote>)WaveAgent.this.neighbours.clone();
                wave_waits.remove(this.wave_agent);
                wave = new Wave(this.wave_action.wave_id, this.wave_agent, wave_waits);
                WaveAgent.this.waves.put(this.wave_action.wave_id, wave);
                state = WaveAgent.PropagateState.FIRST;
            }
            WaveAgent.this.mutex.release();
            
            if(state == WaveAgent.PropagateState.SECOND) {
                wave.first_mutex.acquireUninterruptibly();
                wave.first_mutex.release();
            }
            
            try {
                if(state != WaveAgent.PropagateState.LEEF) wave.mutex.acquireUninterruptibly();
                
                if(state == WaveAgent.PropagateState.LEEF) {
                    this.wave_action.PropagateBefore(WaveAgent.this);
                    this.wave_action.PropagateAfter(WaveAgent.this);
                    this.wave_action.Acknowledge(WaveAgent.this);
                    this.wave_action.LocalEnd(WaveAgent.this);
                    wave_agent.Acknowledge(WaveAgent.this, this.wave_action);
                } else if(state == WaveAgent.PropagateState.SECOND) {
                    wave.Acknowledged(this.wave_agent);
                    WaveAgent.this.mutex.acquireUninterruptibly();
                    if(wave.GetState() != Wave.State.RUNNING) WaveAgent.this.waves.remove(this.wave_action.wave_id);
                    WaveAgent.this.mutex.release();
                    
                    if(wave.GetState() == Wave.State.GLOBALY_ENDED) {
                        this.wave_action.Acknowledge(WaveAgent.this);
                        this.wave_action.LocalEnd(WaveAgent.this);
                        this.wave_action.GlobalEnd(WaveAgent.this);
                    } else if(wave.GetState() == Wave.State.LOCALY_ENDED) {
                        this.wave_action.Acknowledge(WaveAgent.this);
                        this.wave_action.LocalEnd(WaveAgent.this);
                        wave.GetUp().Acknowledge(WaveAgent.this, this.wave_action);
                    } else {
                        this.wave_action.Acknowledge(WaveAgent.this);
                    }
                } else {
                    this.wave_action.PropagateBefore(WaveAgent.this);
                    for (int i = 0;i<wave_waits.size();i++) {
                        wave_waits.get(i).Propagate(WaveAgent.this, this.wave_action);
                    }
                    this.wave_action.PropagateAfter(WaveAgent.this);
                    wave.first_mutex.release();
                }
            } catch (Exception ex) {
                WaveError(ex.getMessage());
            } finally {
                if(state != WaveAgent.PropagateState.LEEF) wave.mutex.release();
            }
        }
    }
    
    private class ThreadAcknowledge extends Thread {
        private WaveAgentRemote wave_agent;
        private WaveAction wave_action;
        
        public ThreadAcknowledge(WaveAgentRemote wave_agent, WaveAction wave_action) {
            this.wave_agent = wave_agent;
            this.wave_action = wave_action;
        }
        
        public void run() {
            Wave wave;
            
            WaveAgent.this.mutex.acquireUninterruptibly();
            wave = WaveAgent.this.waves.get(this.wave_action.wave_id);
            WaveAgent.this.mutex.release();
            
            wave.first_mutex.acquireUninterruptibly();
            wave.first_mutex.release();
            
            try {
                wave.mutex.acquireUninterruptibly();
                wave.Acknowledged(this.wave_agent);
                WaveAgent.this.mutex.acquireUninterruptibly();
                if(wave.GetState() != Wave.State.RUNNING) WaveAgent.this.waves.remove(this.wave_action.wave_id);
                WaveAgent.this.mutex.release();
                
                if(wave.GetState() == Wave.State.GLOBALY_ENDED) {
                    this.wave_action.Acknowledge(WaveAgent.this);
                    this.wave_action.LocalEnd(WaveAgent.this);
                    this.wave_action.GlobalEnd(WaveAgent.this);
                } else if(wave.GetState() == Wave.State.LOCALY_ENDED) {
                    this.wave_action.Acknowledge(WaveAgent.this);
                    this.wave_action.LocalEnd(WaveAgent.this);
                    wave.GetUp().Acknowledge(WaveAgent.this, this.wave_action);
                } else {
                    this.wave_action.Acknowledge(WaveAgent.this);
                }
            } catch (Exception ex) {
                WaveError(ex.getMessage());
            } finally {
                wave.mutex.release();
            }
        }
    }
    
    public int GetNbWaves() {
        return(this.waves.size());
    }
    
    private enum PropagateState {LEEF, FIRST, SECOND};
    
    private String ip;
    private int hl;
    protected Semaphore mutex;
    private Hashtable<WaveId, Wave> waves;
    protected ArrayList<WaveAgentRemote> neighbours;
    
    protected abstract void WaveError(String ex);
    
    public WaveAgent(String ip) throws RemoteException {
        this.ip = ip;
        this.hl = 0;
        this.neighbours = new ArrayList<WaveAgentRemote>();
        this.waves = new Hashtable<WaveId, Wave>();
        this.mutex= new Semaphore(1);
    }
    
    public String GetIp() {
        return(this.ip);
    }
    
    public void Initiate(WaveAction wave_action) {
        new ThreadInitiate(wave_action).start();
    }
    
    public void Propagate(WaveAgentRemote wave_agent, WaveAction wave_action) throws RemoteException {
        new ThreadPropagate(wave_agent, wave_action).start();
    }
    
    public void Acknowledge(WaveAgentRemote wave_agent, WaveAction wave_action) throws RemoteException  {
        new ThreadAcknowledge(wave_agent, wave_action).start();
    }
}
